from abc import ABCMeta, abstractmethod
from collections import defaultdict
from typing import Any

from .listener import Listener


class AbstractContainer(metaclass=ABCMeta):

    def put(self, key: Any, value: Any):
        self._do_put(key, value)

    def get(self, key: Any) -> Any:
        return self._do_get(key)

    def contains(self, key: Any) -> bool:
        return self._do_contains(key)

    @abstractmethod
    def _do_put(self, key: Any, value: Any):
        pass

    @abstractmethod
    def _do_get(self, key: Any) -> Any:
        pass

    @abstractmethod
    def _do_contains(self, key: Any) -> bool:
        pass


class SimpleContainer(AbstractContainer):

    def __init__(self):
        self.__container = {}

    def _do_put(self, key: Any, value: Any):
        self.__container[key] = value

    def _do_get(self, key: Any) -> Any:
        if self.contains(key):
            return self.__container[key]
        else:
            return None

    def _do_contains(self, key: Any) -> bool:
        return key in self.__container


class HierachycalContainer(SimpleContainer):

    def _do_put(self, key: Any, value: Any):
        if "." not in key:
            super()._do_put(key, value)
        else:
            keys = key.split(".")

            if not super()._do_contains(keys[0]):
                super()._do_put(keys[0], dict)

            item = super()._do_get(keys[0])

            for key in keys[1:-1]:
                if key not in item:
                    item[key] = {}
                item = item[key]

            if keys[-1] not in item:
                item[keys[-1]] = {}
            item[keys[-1]] = value

    def _do_get(self, key: Any) -> Any:
        if "." not in key:
            return super()._do_get(key)
        else:
            keys = key.split(".")
            item = super()._do_get(keys[0])

            for key in keys[1:-1]:
                item = item[key]

            return item[keys[-1]]

    def _do_contains(self, key: Any) -> bool:
        if "." not in key:
            return super()._do_contains(key)
        else:
            keys = key.split(".")

            if not super()._do_contains(key[0]):
                return False

            item = super()._do_get(keys[0])
            for key in keys[1:]:
                if key in item:
                    item = item[key]
                else:
                    return False

            return True


class GuardContainer(HierachycalContainer):

    def __init__(self):
        super().__init__()
        self.__on = False
        self.__watch = defaultdict(list)

    def watch(self, key: Any, listener: Listener):
        self.__watch[key].append(listener)

    def unwatch(self, key: Any, listener: Listener):
        self.__watch[key].remove(listener)

    def on(self):
        self.__on = True

    def off(self):
        self.__on = False

    def put(self, key: Any, value: Any):
        self._do_put(key, value)
        self._invoke(key, value)

    def _invoke(self, key: Any, value: Any):
        if self.__on:
            if key in self.__watch:
                for listener in self.__watch[key]:
                    listener.handle(key, value)
